﻿using System;
using System.Collections.Generic;

namespace TradeHelp.Utilities
{
    /// <summary>
    /// Sequence is an abstraction of a data reseqeuncer
    /// </summary>
    public class Sequencer<T>
    {
        private int? _curNum;
        private int _subIndex;
        private readonly List<Data> _data = new List<Data>();

        /// <summary>
        /// Takes as input data of type T and returns all data as a contiguous
        /// block of reseqeunced data.  
        /// </summary>
        /// <param name="data">the data to be re-sequenced</param>
        /// <param name="sequenceNum">the seqence number associated with the data</param>
        /// <param name="initial">indicates if this sequenceNum is the first in the sequence</param>
        /// <returns>contiguous sequence of data</returns>
        public IEnumerable<T> Resequence(T data, int sequenceNum, bool initial)
        {
            if (initial)
            {
                if (_curNum.HasValue)
                    throw new Exception();
                _curNum = sequenceNum;
            }
            else if (sequenceNum < _curNum)
            {
                throw new ArgumentOutOfRangeException();
            }

            var temp = new Data(data, sequenceNum, ++_subIndex);
            var index = _data.BinarySearch(temp);
            if (index > 0)
                throw new Exception();
            _data.Insert(~index, temp);

            return Enumerate();
        }

        /// <summary>
        /// Takes as input a collection of data of type T and returns all data as a contiguous
        /// block of reseqeunced data.
        /// </summary>
        /// <param name="collection">the data to be re-sequenced</param>
        /// <param name="sequenceNum">the seqence number associated with the data</param>
        /// <param name="initial">indicates if this sequenceNum is the first in the sequence</param>
        /// <returns>contiguous sequence of data</returns>
        public IEnumerable<T> Resequence(ICollection<T> collection, int sequenceNum, bool initial)
        {
            Invariant.CheckIsNull(collection, "collection");
            
            if (initial)
            {
                if (_curNum.HasValue)
                    throw new Exception();
                _curNum = sequenceNum;
            }
            else if (sequenceNum < _curNum)
            {
                throw new ArgumentOutOfRangeException();
            }

            if (collection.Count == 0)
            {
                var temp = new Data(sequenceNum, ++_subIndex);
                var index = _data.BinarySearch(temp);
                if (index > 0)
                    throw new Exception();
                _data.Insert(~index, temp);
            }
            else
            {
                foreach (var data in collection)
                {
                    var temp = new Data(data, sequenceNum, ++_subIndex);
                    var index = _data.BinarySearch(temp);
                    if (index > 0)
                        throw new Exception();
                    _data.Insert(~index, temp);
                }
            }

            return Enumerate();
        }


        private IEnumerable<T> Enumerate()
        {
            while (_data.Count > 0 && _data[0].Index1 == _curNum)
            {
                while (_data.Count > 0 && _data[0].Index1 == _curNum)
                {
                    if (_data[0].HasData)
                    {
                        yield return _data[0].TData;
                    }
                    _data.RemoveAt(0);
                }
                _curNum++;
            }
        }

        private struct Data : IComparable<Data>
        {
            public Data(int index1, int index2)
            {
                Index1 = index1;
                Index2 = index2;
                HasData = false;
                TData = default(T);
            }

            public Data(T data, int index1, int index2)
            {
                TData = data;
                Index1 = index1;
                Index2 = index2;
                HasData = true;
            }

            public readonly T TData;
            public readonly int Index1;
            public readonly int Index2;
            public readonly bool HasData;

            public int CompareTo(Data other)
            {
                if (Index1 == other.Index1)
                {
                    return Comparer<int>.Default.Compare(Index2, other.Index2);
                }
                return Comparer<int>.Default.Compare(Index1, other.Index1);
            }
        }
    }
}